﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Markup;

using alib;
using alib.Debugging;
using alib.Enumerable;

using agree.schema;
using agree.configuration;

namespace agree
{
	using Titem = IChartObj<ChartSpan>;

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Filter tokens whose dependencies are not met out of the specified sequence
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[ContentProperty("Tokens")]
	public sealed class ChartDependenciesLoader : m_observable<IActiveObj<Titem>, Titem>, IObserver<Titem>,
		ρρAgreeConfig,
		ρρIsometrics
	{
		public ChartDependenciesLoader(IRtParent parent, IActiveObj<Titem> toks)
			: base(parent, toks)
		{
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		AgreeConfig config;
		public AgreeConfig AgreeConfig
		{
			get { return this.config; }
			set
			{
				if (value == config)	// (also permits 'null')
					return;
				if (value == null)
					throw new ArgumentNullException("AgreeConfig");
				if (config != null)
					throw new PropertyAlreadySetException("AgreeConfig");
				config = value;

				ld = value.LexDependencies;
			}
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		public IActiveObj<Titem> Tokens
		{
			get { return this.t0; }
			set { this.t0 = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		Isometrics im;
		public Isometrics Isometrics
		{
			get { return this.im; }
			set { this.im = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		LexDependenciesConfig ld;
		public LexDependenciesConfig LexDependenciesConfig
		{
			get { return this.ld; }
			set { this.ld = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		/// 
		struct ChartDependency
		{
			public ChartDependency(FsPath target_path, Edge.Flag f)
			{
				Debug.Assert(f > 0);
				this.target_path = target_path;
				this.tu = target_path.im.tu;
				this.f0 = f;
				this.IsSatisfied = false;
			}
			readonly FsPath target_path;
			readonly TypeUtils tu;
			readonly Edge.Flag f0;
			public bool IsSatisfied;

			public bool IsSatisfiedBy(Titem co)
			{
				Debug.Assert(!IsSatisfied);
				Edge.Flag f1;

				if ((f1 = target_path[co].EdgeFlag) > 0)
				{
					if (f0 == f1 || ((f0 & f1) < Edge.Flag.EtmLeafType) &&
						(f0 < f1 ? tu.UnifyTypesHiLo(f0, f1) : tu.UnifyTypesHiLo(f1, f0)) >= 0)
						IsSatisfied = true;
				}
				return IsSatisfied;
			}
		};
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		/// 
		struct DependencyGroup
		{
			public DependencyGroup(Titem tok, ChartDependency[] deps)
			{
				this.tok = tok;
				this.deps = deps;
				this.c_remain = deps.Length;
			}
			public readonly Titem tok;
			public readonly ChartDependency[] deps;
			int c_remain;

			public bool IsSatisfiedBy(TypeUtils tu, IEnumerable<Titem> satisfied)
			{
				/// all dependencies must be satisfied
				foreach (var dpnd in deps)
				{
					/// any disjoint token can satisfy an unsatisfied dependency
					if (!dpnd.IsSatisfied)
						foreach (Titem other in satisfied)
							if (!tok.ProximalContext.Overlaps(other.ProximalContext) && dpnd.IsSatisfiedBy(other))
								if (--c_remain == 0)
									return true;
								else
									goto ok;
					break;
				ok: ;
				}
				return false;
			}
		};
		/// 
		///////////////////////////////////////////////////////////////////////


		List<DependencyGroup> probation;
		FsPathPair[] rg_paths;
		LinkedList<Titem> next;
		public int c_in;

		protected override void start(IActiveObj<Titem> toks)
		{
			this.probation = new List<DependencyGroup>(150);
			this.next = new LinkedList<Titem>();
			this.rg_paths = ld.BootstrapChartDependencyPaths
							  .Select(kvp => new FsPathPair(im, kvp.x, kvp.y))
							  .ToArray();
			toks.Subscribe(this);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// Get chart dependencies for the specified token, if any
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		IEnumerable<ChartDependency> GetDependencies(Titem tok)
		{
			Edge.Flag ef;
			foreach (var cdp in rg_paths)
				if ((ef = cdp.path1[tok].EdgeFlag) > 0)
					yield return new ChartDependency(cdp.path2, ef);
		}

		void IObserver<Titem>.OnNext(Titem tok)
		{
			_add_task();

			Interlocked.Increment(ref c_in);

			var rg_dpnd = GetDependencies(tok).ToArray();

			/// Tokens which have no dependency are accepted.
			if (rg_dpnd.Length == 0)
			{
				next.AddLast(tok);
				_item(tok);
			}
			else
				probation.Add(new DependencyGroup(tok, rg_dpnd));
			_remove_task();
		}

		void IObserver<Titem>.OnCompleted()
		{
			if (probation.Count > 0)
			{
				/// find those probationary tokens for which all of their chart dependencies have at least one satisfied
				/// token (in a non-overlapping chart position) containing a compatible type at the specified path.
				/// 
				/// satisfying tokens are not limited to the initial set, but must spread transitively as new ones become satisfied
				/// so the operation comprises multiple passes
				do
				{
					var current = next;
					next = null;
					for (int ix = 0; ix < probation.Count; ix++)
					{
						var dgrp = probation[ix];
						var tok = dgrp.tok;
						if (tok != null && dgrp.IsSatisfiedBy(im.tu, current))
						{
							/// append to the current satisfied set so that we might avoid an extra pass
							current.AddLast(tok);
							/// but also create a separate list of only the newly satisfied tokens, in case 
							/// we actually do need to do another pass. Plus, if this list is empty (null) after
							/// this pass, then we know we don't have to do that pass
							(next = next ?? new LinkedList<Titem>()).AddLast(tok);
							probation[ix] = default(DependencyGroup);
							_item(tok);
						}
					}
				}
				while (next != null);
			}
			_remove_task();
		}

		void IObserver<Titem>.OnError(Exception error)
		{
			_error(error);
		}

		public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
		{
			foreach (var v in base.Validate(validationContext))
				yield return v;

			if (im == null)
				yield return new ValidationResult("Property 'Isometrics' must be set.");

			if (ld == null)
				yield return new ValidationResult("Property 'LexDependenciesConfig' must be set.");

			if (ld.ChartDependencyScope == agree.configuration.LexDependenciesConfig.DependencyScope.TwoWay)
				yield return new ValidationResult("two-way chart dependencies not implemented, use 'unidirectional-chart-dependencies' if possible");

			if (ld.BootstrapChartDependencyPaths == null || ld.BootstrapChartDependencyPaths.Length == 0)
				yield return new ValidationResult("error");
		}
	};
}
